// StaleTupleSpaceHandler.java

package com.ibm.tspaces.examples.handler;

/*
**  Licensed Materials - Property of IBM
**
**  (C) COPYRIGHT IBM Corp. 1996, 1997  All rights reserved
**
**  US Government Users Restricted Rights - Use, duplication or
**  disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
**
**/


import java.util.*;
import com.ibm.tspaces.*;
import com.ibm.tspaces.server.*;
import com.ibm.tspaces.server.handler.*;




/**
** 
** This class is a handler for a "StaleTupleSpace" this is a space that will clean up tuples
** that are in there too long. Currenly this only means from creation time, ie reading them
** or performing some other operation will not update a tuple's timestamp. But of course,
** the handlers for those commands could be re-implemented here to update the timestamps.
** 
** The handler only operates on Tuples of type StaleTuple and these tuples must always have a long in the
** first field that is a timestamp field. The handler will initialize this value on write and return
** the value to the user if they want to use it for something eg as a unique index ( assuming currentTimeMills
** returns unique values).
**
** Two commands are implemented:
** TupleSpace.WRITE -- stamps the correct time in the first field before using the basic write command.
** START						-- start the daemon on the server (only call this once). This starts 
** a seperate daemon thread on the server that usually sleeps, but occasionally wakes up
** to check for stale tuples which it will delete.
**
** @see TSFactory
** @see TSFExtendable
** @see TSHandler
** @author Peter Wyckoff
** @author John Thomas 
** @version $Revision: 2.1.2.1 $ $Date: 2000/02/05 14:49:58 $
*************************************************************************
*/


public class StaleTupleSpaceHandler extends  TSHandler implements Runnable {

	
	/**
	** the command to start this handler up as a daemon on the server
	** that periodically deletes stale tuples
	*/
	
	public static final String START 	= "StaleTupleStart";
	
	/**
	** the following variables are used by the run method which is run as a daemon thread
	** deleting tuples that have gotten stale every so often.
	** 
	*/
	
 	
	
	private TS 								_ts 								= null;	
	
	/**
	**  How log to delay between checks for stale Tuple (ms) 
	*/
	private long							_howOften						= 0;
	
	private TSResponse 			  _communicator				= null;	
  private String            _user;
  private String            _clientID;
  
  /**
  ** Until the following switch is set True, the Write 
  ** command handler will not do anything.
  */
  static private boolean    _enabled           = false;
	/*
  ***************************************************************************
  ** ** constructor **
  ** *****************
  */
  /**
	** empty constructor called by the factory methods to create the new handler
  ***************************************************************************
  */
	public StaleTupleSpaceHandler() { 
	  Debug.out("StaleTupleSpaceHandler()");
	}
	
  
	/*
  ***************************************************************************
  ** ** constructor **
  ** *****************
  */
  /**
  ** This constructor is used to create an instance of StaleTupleSpaceHandler
  ** that will be used to run the thread that periodically deletes stale tuples.
  **
	** @param ts_ the tuple space to do ops on
	** @param howOften the time (ms) to wait between checks
	** @param clientID_ Transaction id
	** @param communicator_ the io handle back to the client
  ***************************************************************************
  */
	StaleTupleSpaceHandler(TS ts_,  long howOften_,
												 String clientID_, TSResponse communicator_
								 				 ) {
		Debug.out("StaleTupleSpaceHandler(ts,argtuple)");
						 				 	
		_communicator = communicator_;
		_ts = ts_;
	
		_howOften = howOften_;
	  _clientID = clientID_;
	  
		}

	
	
	/*
  ***************************************************************************
  ** ** handles **
  ** *************
  */
  /**
  ** Do I handle this command on this argument?
  ** For now, always say yes because the factory will only call us on 
  ** the command strings that we handle and we operate on all types of tuples
  ** When each handler only handles one command, can tighten this up.
  ** @param commandString the command
  ** @param argTuple the agument to the command
  ** @return true if I am so inclined as to handle this command false else
  ***************************************************************************
  */
	public final boolean handles(String commandString, SuperTuple argTuple) {
		// return argTuple instanceof StaleTuple;
				return true;
	}

	
	/*
  ** ************************************************************************
  ** ** attributes **
  ** ****************
  */
  /**
  ** What type of access control attributes are required to use the handler?
  ** If a zero length array is returned this means no attributes are needed.
  ** @param commandStr the command in question
  ** @param argTuple The argument for the command.
  ** @return an array of the attributes need for this command --- at least a zero length
  ** array is always returned except if this is not a command for this
  ** handler in which case null is returned, but that should never happen.
  **  
  ***************************************************************************
  */
  public AccessAttribute [] 
  attributes(String cmdString, SuperTuple argTuple) throws TSHandlerException {
  
  	AccessAttribute admin[] = { AccessAttribute._ADMIN_ATTRIBUTE };
    AccessAttribute write[] = { AccessAttribute._WRITE_ATTRIBUTE};

      if(cmdString.equals(TupleSpace.WRITE)  ) 
        return write;
      else 
        return admin;
      
  } // attributes


  /*
  ** ************************************************************************
  ** ** command **
  ** *************
  */
  /**
  ** Implement one or more "commands" (usually one).
  **
  ** @param ts The TupleSpace instance on which the command is executed.
  ** @param cmdString The cmd to be invoked
  ** @param argTuple The argument for the command.
  ** @param clientID_  The client Transaction ID
  ** @param communicator_ the io handle back to the client
  ** @param user_ the user issuing the command
  ** @return a "result" tuple that is the output of the command. 
  ** @exception TSHandlerException if this handler cannot complete the command
  **							or there was some problem encountered while executing the
  **							command.
  **
  ***************************************************************************
  */

public SuperTuple command( TS ts, String cmdString, SuperTuple argTuple, 
                             String clientID_,
  													 TSResponse communicator_, String user_	)
    throws TSHandlerException  /*, TupleSpaceClientException */
  {
  		// StaleTupleSpaceHandler this handler implements the functionality needed for
	 		// a tuple space that cleans up old tuples.
			// Tuple should be ( Long, other stuff) 
    _user = user_;  		
		_communicator = communicator_; 
  	
  
		SuperTuple retValue = new Tuple();
		try {
			if(cmdString.equals(TupleSpace.WRITE)  ) {
				Debug.out("StaleTupleHandler: WRITE");
			  // until Write handler is enabled, we just do standard Write
        if ( _enabled ) {  
				  if (argTuple instanceof StaleTuple) {
				  	((StaleTuple)argTuple).updateExpirationDate();
				  }	
				} else {
          Debug.out(0,"StaleTuple: Write not enabled");
        }
				final TSHandler simpleWrite = ts.mostBasicHandler( TupleSpace.WRITE, argTuple);
				retValue = simpleWrite.command( ts, TupleSpace.WRITE, argTuple, clientID_, communicator_, user_);
			  Debug.out("simpleWrite: "+retValue);
			  
			} else if(cmdString.equals(StaleTupleSpaceHandler.START))  {
			  // Handle the START command
				  Debug.out("StaleTupleHandler: START");
			  
				// Get the parameter that says how often we want to check
			
				Field first = argTuple.getField(0);			  
				final long howOften = ((Long)first.getValue()).longValue();
					
				// create and start up a daemon thread to be the tuple space stale tuple deleter.
					
				StaleTupleSpaceHandler newExecuter = new StaleTupleSpaceHandler(ts,howOften,clientID_, communicator_);

				Thread doTheLoop = new Thread(newExecuter,"StaleKiller");
				doTheLoop.setDaemon( true); // this is a daemon so set it on
				doTheLoop.start();
				_enabled = true;   // enable the Write command
        Debug.out(0,"StaleTuple: Write enabled");
			} else {   
				throw new TSHandlerException("Invalid command: "+cmdString);
			}
    } catch(TupleSpaceException e) {
      Debug.out(Debug.ALWAYS,e);
      throw new TSHandlerException(e.getMessage());
    }
		return retValue;
	} // command
	
	/*
  ***************************************************************************
  ** ** run **
  ** *********
  */
  /**
	** actually run as a background daemon and do the deletions of stale tuples.
	** 
  ***************************************************************************
  */
	public void run(){
		
	  try {
      
      long throwOutBeforeThis = System.currentTimeMillis();
	  	StaleTimestamp timestamp = new StaleTimestamp(throwOutBeforeThis);
      StaleTuple template = new StaleTuple(timestamp);

      int ct =0;
	  	String myClientID;	  	
	  	TransactionManager transMgr = TSServer.getTransMgr();
	  	
      // need the basic delete not somebody elses definition of it, I think
      TSHandler simpleDelete = _ts.mostBasicHandler( TupleSpace.DELETE, template);          
      Debug.out("StaleTupleHandler.run():" + _howOften );
      while(true) {
      	// generate a new Transaction Identifier and tell TransactionManager
      	ct++;
      	myClientID = _clientID+"START"+ct;        
        int transID = transMgr.beginTrans(myClientID);

      	throwOutBeforeThis = System.currentTimeMillis();
      	// Create a subclass of Field that contains the current date/time
      	timestamp = new StaleTimestamp(throwOutBeforeThis);
        template = new StaleTuple(timestamp);
      
      	Debug.out("template="+template);
        SuperTuple delTuple = simpleDelete.command( _ts, 
                                     TupleSpace.DELETE, 
                                     template, 
                                     myClientID, 
                                     _communicator, 
                                     _user);
        Debug.out(0,"StaleTuples deleted " + delTuple);
      	// commit the transaction
      	transID = transMgr.commitTrans(myClientID);
      	// delay until it is time to check again
      	delay( (int)_howOften) ;
      } // end while loop
	  	
    } catch(TupleSpaceException e) { 
        Debug.out(0,e);
    }
				
		
			
	
	
} // end run
	
	
	
	
  /*
  ***************************************************************************
  ** ** main **
  ** *********
  */
  /**
  ** This is code to test the handler by simulating what a client 
  ** application would do to load a special handler and then invode the 
  ** the new commands.Normally this code would be part of the application
  ** that is going to to make use of the handler.  
  
  **
  ** <p>
  ** The basic steps are:
  ** <p>
  **   Create a TupleSpace. Remember that this function
  **   must be performed by a user that has "admin" 
  **   authority for the server
  ** <p>
  **   issue an AddFactory specifing a factory that supports
  **   downlable handlers.  The distributed TSFExtendable
  **   can be used for this..
  ** <p>
  **   Create the Handler object.
  **  <p> 
  **   Issue an addHandler command for each of the commands that this handler 
  **   will support.
  **  
  **  <p> 
  **   The TupleSpace and its new commands are now ready for use so this 
  **   routine will write a StaleTuple to the Space and then issue the 
  **   START command which will actually start a thread at the server 
  **   which will delete tuples when they expire. 
  ** 
  **  <p> 
  **   This routine then writes some more StaleTuples just to verify that 
  **   it is really working.
  **<p>
  ** For this relatively simple test, we hardcode all of the values that
  ** we need.  
  ***************************************************************************
  */
	
public static void
main( String[] argv) {
	
  String Host = "localhost";
  String TSName = "Stale1";
	
	// We need to have admin authorithy.  Make sure the userid and password 
	// are correct.
  String MyUserid = "sysadmin";
  String MyPassword = "admin";
	
    // Check for arguments
    for (int i=0; i<argv.length; i++) {
      if (argv[i].equals("-D")) {  // should be last argument        
        Debug.setDebugOn(true);
        //TupleSpace.setDebug(true);
        continue;
      }
      switch (i) {
        case 0: 
          Host = argv[i];
          break;
        case 1:
          TSName = argv[i];
          break;
        case 2:
          MyUserid = argv[i];
          break;
        case 3:
          MyPassword = argv[i];
          break;
      }
    }    
		final String FactoryName = "com.ibm.tspaces.server.handler.TSFExtendable";
    
	  
	  TupleSpace ts = null;
	  try {
      // create a space to work on  
       ts = new TupleSpace(TSName,
               Host,TupleSpace.DEFAULTPORT,
              null,null,  
              MyUserid,MyPassword);  
      
      // Add a Factory that can be extended with new commands
      ts.addFactory( new Class[] { Class.forName(FactoryName)} );

      // now let's create the handler class and download it
      // it actually downloads itself twice. once for WRITE and once for START
      StaleTupleSpaceHandler dh = new StaleTupleSpaceHandler();
      
      ts.addHandler( TupleSpace.WRITE, new Class[] { dh.getClass()} );
      //try { Thread.sleep(1000); } catch (Exception e) {}
      ts.addHandler( StaleTupleSpaceHandler.START, new Class[] { dh.getClass()} );
    } catch(Exception e) {
      Debug.out(0, e);
      e.printStackTrace();
    }
    
	  try {	
      	  	
      // throw a tuple in there to be killed
      StaleTuple test1 = new StaleTuple("key1","some data");
	  	test1.setTimeToLive(1000);   // 1 second of life
	  	TupleID uid = ts.write(test1);
	  	Debug.out(uid);
	  	Tuple readback = ts.scan(test1);
	  	Debug.out(readback);
      //
	  	// The modified write command returns the uniqueId of the generated Tuple.  
	  	// If you want to use it, you have to
	  	
      //Debug.out("Write returned: "+return1);
	  	delay(2000);
	  	
	  	// let's start the stale tuple killer with a template 
	  	// contains the value that will
	  	// indicate how often the space should be checked for 
	  	// candidates that are elgible to be deleted.
	  	// 
	  	// setup a tuple to tell how often we want to check
      
	  	Tuple argtuple = new Tuple(new Long(5*1000));
      
	  	
      ts.command( StaleTupleSpaceHandler.START,argtuple); 
      Debug.out("Issued START command");                   
      
      // let's throw some more
	  	// tuples in there and make sure they are killed
      
    
	  	for(int i=0;i<10;i++) {
	  		StaleTuple st = new StaleTuple( "Key "+i, "Data for key "+i);
        st.setTimeToLive(10*1000);
	  		ts.command( TupleSpace.WRITE, st );
        delay(1*1000);
	  	}
	 
	  	StaleTuple template = new StaleTuple();
	  	int n = ts.countN(template);
      Debug.out(0,"Number of tuples = "+n); 
      // let's sleep for a while and then see how many tuples are left
	  	// It should be 0
	  	Debug.out(0,"Delay for 15 seconds");
      delay(60*1000);
      n = ts.countN(template);
      if ( n == 0) 
        Debug.out(0,"OK. Remaining = "+n);
	    else 
        Debug.out(0,"Failed.  all tuples not deleted");
    } catch(Exception e) {
      Debug.out(0, e);
      e.printStackTrace();
    }

}

	
	
  /*
  ***************************************************************************
  ** ** delay **
  ** *********
  */
  /**
  ** a handy routine to delay the specified number of MS
  **
  ** @param n Number of MS to delay.  
  */

	private static void 
		delay(int n) {
			try {
        Thread.sleep(n);
      } catch(InterruptedException e)      { 
      }
		}
} // End Class StaleTupleSpaceHandler
/*
$History: StaleTupleSpaceHandler.java $
 * 
 * *****************  Version 4  *****************
 * User: Jthomas      Date: 3/26/99    Time: 12:56p
 * Updated in $/GCS/Development/TSpaces/Java/com/ibm/tspaces/examples/handler
 * Change definition of Tuple uniqueID to a TupleID object instead of an
 * int
 * 
 * *****************  Version 3  *****************
 * User: Jthomas      Date: 3/23/99    Time: 9:45a
 * Updated in $/GCS/Development/TSpaces/Java/com/ibm/tspaces/examples/handler
 * Fix example to match uniqueid change to write
 * 
 * *****************  Version 2  *****************
 * User: Jthomas      Date: 1/29/99    Time: 9:59a
 * Updated in $/GCS/Development/TSpaces/Java/com/ibm/tspaces/examples/handler
 * new Field methods
 * Debug stuff
 * 
 * *****************  Version 1  *****************
 * User: Jthomas      Date: 11/17/98   Time: 10:14a
 * Created in $/GCS/Development/TSpaces/Java/com/ibm/tspaces/examples/handler
 * Downloadable command handler example
 * 
 
*/
/* $Log: StaleTupleSpaceHandler.java,v $
/* Revision 2.1.2.1  2000/02/05 14:49:58  jthomas
/* Add enable switch to the example
/*
/* Revision 2.1  1999/11/05 22:22:38  estesp
/* Update revision number to 2.1 on all files
/*
/* Revision 1.1.1.1  1999/11/05 16:24:53  estesp
/* Imported 2.1.0 release into Austin CVS
/*
 * Revision 1.3  1999/09/12 01:30:43  jthomas
 * Minor fixes so it runs cleaner
 *
 * Revision 1.2  1999/06/17 05:39:38  thodes
 *   Updated the inital checkin to append the "Log" field and
 * convert CR/LF to CR (for win/unix interop).
 *
 *   The inital checkin lower-cased directrory names, and omitted
 * many dirs/files that will added later, e.g., MoDAL stuff.
 *
 * All of the above will need to be done to all VSS-to-CVS
 * converted sources.  In summary, in moving from VSS to CVS, do this:
 *  - lower-case dir names
 *  - CR/LF --> CR
 *  - append CVS "Log" entties
 *
 * (a script that helps with #2 and #3 is in
 *     tspaces1:~thodes/bin/update-keywords.sh)
 * 
 */